package org.rich.charlesmurphy.core.analyze;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.ddevil.data.set.BeanDataSet;
import org.ddevil.data.set.DataSet;
import org.rich.charlesmurphy.model.Request;
import org.rich.charlesmurphy.model.RequestGroup;
import org.rich.charlesmurphy.model.ResourceType;

/**
 * {@link RequestSetAnalyzer} that will group requests based on their timestamp.
 * So each resource request is added to the request group for that page that has the nearest timestamp.
 * <strong>This is NOT thread safe</strong>
 * @author Rich
 */
public class TimeBasedAnalyzer extends AbstractAnalyzer {

	private Map<String, List<RequestGroup>> groupsByClient = new HashMap<String, List<RequestGroup>>();

	/**
	 * {@inheritDoc}
	 */
	public DataSet<RequestGroup> analyze(DataSet<Request> requests) {
		DataSet<RequestGroup> grouped = new BeanDataSet<RequestGroup>();
		groupsByClient.clear();
		
		// first find all the pages, and create our groupByClient data structure
		for (Request r : requests) {
			if (accept(r) && r.getResourceType() == ResourceType.PAGE) {
				RequestGroup group = new RequestGroup(r);
				grouped.importRecord(group);
				addGroup(r.getRemoteHost(), group);				
			}
		}

		// loop again and process no page requests, find the page group they belong to
		for (Request r : requests) {
			if (accept(r) && r.getResourceType() != ResourceType.PAGE) {
				RequestGroup group = findGroupForRequest(r);
				
				if (group != null) {
					getLog().trace("Adding: [{}], to the request group: [{}]", r.getPath(), group.getPath());
					// integrate data into current group being worked
					group.addRequest(r);
				}
				else {
					// must be due to a bad page matching regex or partial log file, just ignore
					getLog().debug("Request: [{}] does not associate with an existing group. Skipping.", r);
				}
			}
		}
	
		getLog().info("Done creating Grouped Request records.");
		return grouped;
	}

	private void addGroup(String client, RequestGroup group) {
		List<RequestGroup> groupsForClient = groupsByClient.get(client);
		if (groupsForClient == null) {
			groupsForClient = new ArrayList<RequestGroup>();
			groupsByClient.put(client, groupsForClient);
		}
		groupsForClient.add(group);
	}

	
	private RequestGroup findGroupForRequest(Request request) {
		// TODO need an enhanced data structure here for faster searching
		List<RequestGroup> groupsForClient = groupsByClient.get(request.getRemoteHost());
		if (groupsForClient == null) {
			return null;
		}

		// this list will be in ascending order by time
		long requestTime = request.getStartTime().getTime();
		long diff = -1;
		RequestGroup bestMatch = null;
		for (RequestGroup page : groupsForClient) {
			long groupTime = page.getTime().getTime();
			long thisDiff = Math.abs(groupTime - requestTime);
			if (diff == -1) {
				// first check
				bestMatch = page;
				diff = thisDiff;
			}
			else if (thisDiff < diff) {
				// we found a closer, better match
				bestMatch = page;
				diff = thisDiff;
			}
			else if (thisDiff > diff){
				// since pages are ordered by time, if thisDiff is greater then our last recorded diff
				// we are moving away and we've already found the best match
				break;
			}
		}
		
		return bestMatch;
	}


}
